package com.bbm.common.cmm.util;

import java.io.PrintStream;
import java.security.InvalidKeyException;

/**
 * @Class Name : CipherUtil.java
 * @Description : 데이터 암복호화 처리 관련 유틸리티
 * @author 기술 및 표준화
 * @since 2011. 06. 17
 * @version 1.0
 * @see <pre>
 *  == 개정이력(Modification Information) ==
 *   
 *   수정일              수정자           수정내용
 *  -------           --------    ---------------------------
 *   2011. 06. 17       김일수            최초 생성
 * 
 * </pre>
 */

/** 
 * ARIA 알고리즘.
 */
class ARIAEngine {
    private static final char[] HEX_DIGITS = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };

    private static final int[][] KRK = { 
	{ 0x517cc1b7, 0x27220a94, 0xfe13abe8, 0xfa9a6ee0 }, 
	{ 0x6db14acc, 0x9e21c820, 0xff28b1d5, 0xef5de2b0 },
	{ 0xdb92371d, 0x2126e970, 0x03249775, 0x04e8c90e } };
    
    private static final byte[] S1 = new byte[256];
    private static final byte[] S2 = new byte[256];
    private static final byte[] X1 = new byte[256];
    private static final byte[] X2 = new byte[256];
    private static final int[] TS1 = new int[256];
    private static final int[] TS2 = new int[256];
    private static final int[] TX1 = new int[256];
    private static final int[] TX2 = new int[256];

    // Static initializer.  For setting up the tables
    static {
	int[] exp = new int[256];
	int[] log = new int[256];

	exp[0] = 1;
	for (int i = 1; i < 256; i++) {
	    int j = (exp[i - 1] << 1) ^ exp[i - 1];
	    if ((j & 0x100) != 0)
		j ^= 0x11b;
	    exp[i] = j;
	}
	for (int i = 1; i < 255; i++)
	    log[exp[i]] = i;

	int[][] A = { 
		{ 1, 0, 0, 0, 1, 1, 1, 1 }, 
		{ 1, 1, 0, 0, 0, 1, 1, 1 }, 
		{ 1, 1, 1, 0, 0, 0, 1, 1 }, 
		{ 1, 1, 1, 1, 0, 0, 0, 1 },
		{ 1, 1, 1, 1, 1, 0, 0, 0 }, 
		{ 0, 1, 1, 1, 1, 1, 0, 0 }, 
		{ 0, 0, 1, 1, 1, 1, 1, 0 }, 
		{ 0, 0, 0, 1, 1, 1, 1, 1 } };
	int[][] B = { 
		{ 0, 1, 0, 1, 1, 1, 1, 0 }, 
		{ 0, 0, 1, 1, 1, 1, 0, 1 }, 
		{ 1, 1, 0, 1, 0, 1, 1, 1 }, 
		{ 1, 0, 0, 1, 1, 1, 0, 1 },
		{ 0, 0, 1, 0, 1, 1, 0, 0 }, 
		{ 1, 0, 0, 0, 0, 0, 0, 1 }, 
		{ 0, 1, 0, 1, 1, 1, 0, 1 }, 
		{ 1, 1, 0, 1, 0, 0, 1, 1 } };

	for (int i = 0; i < 256; i++) {
	    int t = 0, p;
	    if (i == 0)
		p = 0;
	    else
		p = exp[255 - log[i]];
	    for (int j = 0; j < 8; j++) {
		int s = 0;
		for (int k = 0; k < 8; k++) {
		    if (((p >>> (7 - k)) & 0x01) != 0)
			s ^= A[k][j];
		}
		t = (t << 1) ^ s;
	    }
	    t ^= 0x63;
	    S1[i] = (byte)t;
	    X1[t] = (byte)i;
	}
	for (int i = 0; i < 256; i++) {
	    int t = 0, p;
	    if (i == 0)
		p = 0;
	    else
		p = exp[(247 * log[i]) % 255];
	    for (int j = 0; j < 8; j++) {
		int s = 0;
		for (int k = 0; k < 8; k++) {
		    if (((p >>> k) & 0x01) != 0)
			s ^= B[7 - j][k];
		}
		t = (t << 1) ^ s;
	    }
	    t ^= 0xe2;
	    S2[i] = (byte)t;
	    X2[t] = (byte)i;
	}

	for (int i = 0; i < 256; i++) {
	    TS1[i] = 0x00010101 * (S1[i] & 0xff);
	    TS2[i] = 0x01000101 * (S2[i] & 0xff);
	    TX1[i] = 0x01010001 * (X1[i] & 0xff);
	    TX2[i] = 0x01010100 * (X2[i] & 0xff);
	}
    }

    //private byte[] p_arr;
    static byte[] mk = new byte[32];
    private int keySize = 0;
    private int numberOfRounds = 0;
    private byte[] masterKey = null;
    private int[] encRoundKeys = null, decRoundKeys = null;

    public ARIAEngine(int keySize) throws InvalidKeyException {
	setKeySize(keySize);
    }

    void reset() {
	this.keySize = 0;
	this.numberOfRounds = 0;
	this.masterKey = null;
	this.encRoundKeys = null;
	this.decRoundKeys = null;
    }

    int getKeySize() {
	return this.keySize;
    }

    void setKeySize(int keySize) throws InvalidKeyException {
	this.reset();
	if (keySize != 128 && keySize != 192 && keySize != 256)
	    throw new InvalidKeyException("keySize=" + keySize);
	this.keySize = keySize;
	switch (keySize) {
	case 128:
	    this.numberOfRounds = 12;
	    break;
	case 192:
	    this.numberOfRounds = 14;
	    break;
	case 256:
	    this.numberOfRounds = 16;
	    break;
	default : 
        break;
	}
    }

    void setKey(byte[] masterKey) throws InvalidKeyException {
	if (masterKey.length * 8 < keySize)
	    throw new InvalidKeyException("masterKey size=" + masterKey.length);
	this.decRoundKeys = null;
	this.encRoundKeys = null;
	this.masterKey = (byte[])masterKey.clone();
    }

    void setupEncRoundKeys() throws InvalidKeyException {
	if (this.keySize == 0)
	    throw new InvalidKeyException("keySize");
	if (this.masterKey == null)
	    throw new InvalidKeyException("masterKey");
	if (this.encRoundKeys == null)
	    this.encRoundKeys = new int[4 * (this.numberOfRounds + 1)];
	this.decRoundKeys = null;
	doEncKeySetup(this.masterKey, this.encRoundKeys, this.keySize);
    }

    void setupDecRoundKeys() throws InvalidKeyException {
	if (this.keySize == 0)
	    throw new InvalidKeyException("keySize");
	if (this.encRoundKeys == null)
	    if (this.masterKey == null)
		throw new InvalidKeyException("masterKey");
	    else
		setupEncRoundKeys();
	this.decRoundKeys = (int[])encRoundKeys.clone();
	doDecKeySetup(this.decRoundKeys, this.keySize);
    }

    void setupRoundKeys() throws InvalidKeyException {
	setupDecRoundKeys();
    }

    private static void doCrypt(byte[] i, int ioffset, int[] rk, int nr, byte[] o, int ooffset) {
	int t0, t1, t2, t3, j = 0;

	t0 = toInt(i[0 + ioffset], i[1 + ioffset], i[2 + ioffset], i[3 + ioffset]);
	t1 = toInt(i[4 + ioffset], i[5 + ioffset], i[6 + ioffset], i[7 + ioffset]);
	t2 = toInt(i[8 + ioffset], i[9 + ioffset], i[10 + ioffset], i[11 + ioffset]);
	t3 = toInt(i[12 + ioffset], i[13 + ioffset], i[14 + ioffset], i[15 + ioffset]);

	for (int r = 1; r < nr / 2; r++) {
	    t0 ^= rk[j++];
	    t1 ^= rk[j++];
	    t2 ^= rk[j++];
	    t3 ^= rk[j++];
	    t0 = TS1[(t0 >>> 24) & 0xff] ^ TS2[(t0 >>> 16) & 0xff] ^ TX1[(t0 >>> 8) & 0xff] ^ TX2[t0 & 0xff];
	    t1 = TS1[(t1 >>> 24) & 0xff] ^ TS2[(t1 >>> 16) & 0xff] ^ TX1[(t1 >>> 8) & 0xff] ^ TX2[t1 & 0xff];
	    t2 = TS1[(t2 >>> 24) & 0xff] ^ TS2[(t2 >>> 16) & 0xff] ^ TX1[(t2 >>> 8) & 0xff] ^ TX2[t2 & 0xff];
	    t3 = TS1[(t3 >>> 24) & 0xff] ^ TS2[(t3 >>> 16) & 0xff] ^ TX1[(t3 >>> 8) & 0xff] ^ TX2[t3 & 0xff];
	    t1 ^= t2;
	    t2 ^= t3;
	    t0 ^= t1;
	    t3 ^= t1;
	    t2 ^= t0;
	    t1 ^= t2;
	    t1 = badc(t1);
	    t2 = cdab(t2);
	    t3 = dcba(t3);
	    t1 ^= t2;
	    t2 ^= t3;
	    t0 ^= t1;
	    t3 ^= t1;
	    t2 ^= t0;
	    t1 ^= t2;

	    t0 ^= rk[j++];
	    t1 ^= rk[j++];
	    t2 ^= rk[j++];
	    t3 ^= rk[j++];
	    t0 = TX1[(t0 >>> 24) & 0xff] ^ TX2[(t0 >>> 16) & 0xff] ^ TS1[(t0 >>> 8) & 0xff] ^ TS2[t0 & 0xff];
	    t1 = TX1[(t1 >>> 24) & 0xff] ^ TX2[(t1 >>> 16) & 0xff] ^ TS1[(t1 >>> 8) & 0xff] ^ TS2[t1 & 0xff];
	    t2 = TX1[(t2 >>> 24) & 0xff] ^ TX2[(t2 >>> 16) & 0xff] ^ TS1[(t2 >>> 8) & 0xff] ^ TS2[t2 & 0xff];
	    t3 = TX1[(t3 >>> 24) & 0xff] ^ TX2[(t3 >>> 16) & 0xff] ^ TS1[(t3 >>> 8) & 0xff] ^ TS2[t3 & 0xff];
	    t1 ^= t2;
	    t2 ^= t3;
	    t0 ^= t1;
	    t3 ^= t1;
	    t2 ^= t0;
	    t1 ^= t2;
	    t3 = badc(t3);
	    t0 = cdab(t0);
	    t1 = dcba(t1);
	    t1 ^= t2;
	    t2 ^= t3;
	    t0 ^= t1;
	    t3 ^= t1;
	    t2 ^= t0;
	    t1 ^= t2;
	}
	t0 ^= rk[j++];
	t1 ^= rk[j++];
	t2 ^= rk[j++];
	t3 ^= rk[j++];
	t0 = TS1[(t0 >>> 24) & 0xff] ^ TS2[(t0 >>> 16) & 0xff] ^ TX1[(t0 >>> 8) & 0xff] ^ TX2[t0 & 0xff];
	t1 = TS1[(t1 >>> 24) & 0xff] ^ TS2[(t1 >>> 16) & 0xff] ^ TX1[(t1 >>> 8) & 0xff] ^ TX2[t1 & 0xff];
	t2 = TS1[(t2 >>> 24) & 0xff] ^ TS2[(t2 >>> 16) & 0xff] ^ TX1[(t2 >>> 8) & 0xff] ^ TX2[t2 & 0xff];
	t3 = TS1[(t3 >>> 24) & 0xff] ^ TS2[(t3 >>> 16) & 0xff] ^ TX1[(t3 >>> 8) & 0xff] ^ TX2[t3 & 0xff];
	t1 ^= t2;
	t2 ^= t3;
	t0 ^= t1;
	t3 ^= t1;
	t2 ^= t0;
	t1 ^= t2;
	t1 = badc(t1);
	t2 = cdab(t2);
	t3 = dcba(t3);
	t1 ^= t2;
	t2 ^= t3;
	t0 ^= t1;
	t3 ^= t1;
	t2 ^= t0;
	t1 ^= t2;

	t0 ^= rk[j++];
	t1 ^= rk[j++];
	t2 ^= rk[j++];
	t3 ^= rk[j++];
	o[0 + ooffset] = (byte)(X1[0xff & (t0 >>> 24)] ^ (rk[j] >>> 24));
	o[1 + ooffset] = (byte)(X2[0xff & (t0 >>> 16)] ^ (rk[j] >>> 16));
	o[2 + ooffset] = (byte)(S1[0xff & (t0 >>> 8)] ^ (rk[j] >>> 8));
	o[3 + ooffset] = (byte)(S2[0xff & (t0)] ^ (rk[j]));
	o[4 + ooffset] = (byte)(X1[0xff & (t1 >>> 24)] ^ (rk[j + 1] >>> 24));
	o[5 + ooffset] = (byte)(X2[0xff & (t1 >>> 16)] ^ (rk[j + 1] >>> 16));
	o[6 + ooffset] = (byte)(S1[0xff & (t1 >>> 8)] ^ (rk[j + 1] >>> 8));
	o[7 + ooffset] = (byte)(S2[0xff & (t1)] ^ (rk[j + 1]));
	o[8 + ooffset] = (byte)(X1[0xff & (t2 >>> 24)] ^ (rk[j + 2] >>> 24));
	o[9 + ooffset] = (byte)(X2[0xff & (t2 >>> 16)] ^ (rk[j + 2] >>> 16));
	o[10 + ooffset] = (byte)(S1[0xff & (t2 >>> 8)] ^ (rk[j + 2] >>> 8));
	o[11 + ooffset] = (byte)(S2[0xff & (t2)] ^ (rk[j + 2]));
	o[12 + ooffset] = (byte)(X1[0xff & (t3 >>> 24)] ^ (rk[j + 3] >>> 24));
	o[13 + ooffset] = (byte)(X2[0xff & (t3 >>> 16)] ^ (rk[j + 3] >>> 16));
	o[14 + ooffset] = (byte)(S1[0xff & (t3 >>> 8)] ^ (rk[j + 3] >>> 8));
	o[15 + ooffset] = (byte)(S2[0xff & (t3)] ^ (rk[j + 3]));
    }

    void encrypt(byte[] i, int ioffset, byte[] o, int ooffset) throws InvalidKeyException {
	if (this.keySize == 0)
	    throw new InvalidKeyException("keySize");
	if (this.encRoundKeys == null)
	    if (this.masterKey == null)
		throw new InvalidKeyException("masterKey");
	    else
		setupEncRoundKeys();
	doCrypt(i, ioffset, this.encRoundKeys, this.numberOfRounds, o, ooffset);
    }

    byte[] encrypt(byte[] i, int ioffset) throws InvalidKeyException {
	byte[] o = new byte[16];
	this.encrypt(i, ioffset, o, 0);
	return o;
    }

    void decrypt(byte[] i, int ioffset, byte[] o, int ooffset) throws InvalidKeyException {
	if (this.keySize == 0)
	    throw new InvalidKeyException("keySize");
	if (this.decRoundKeys == null)
	    if (this.masterKey == null)
		throw new InvalidKeyException("masterKey");
	    else
		setupDecRoundKeys();
	doCrypt(i, ioffset, this.decRoundKeys, this.numberOfRounds, o, ooffset);
    }

    byte[] decrypt(byte[] i, int ioffset) throws InvalidKeyException {
	byte[] o = new byte[16];
	this.decrypt(i, ioffset, o, 0);
	return o;
    }

    private static void doEncKeySetup(byte[] mk, int[] rk, int keyBits) {
	int t0, t1, t2, t3, q, j = 0;
	int[] w0 = new int[4];
	int[] w1 = new int[4];
	int[] w2 = new int[4];
	int[] w3 = new int[4];

	w0[0] = toInt(mk[0], mk[1], mk[2], mk[3]);
	w0[1] = toInt(mk[4], mk[5], mk[6], mk[7]);
	w0[2] = toInt(mk[8], mk[9], mk[10], mk[11]);
	w0[3] = toInt(mk[12], mk[13], mk[14], mk[15]);

	q = (keyBits - 128) / 64;
	t0 = w0[0] ^ KRK[q][0];
	t1 = w0[1] ^ KRK[q][1];
	t2 = w0[2] ^ KRK[q][2];
	t3 = w0[3] ^ KRK[q][3];
	t0 = TS1[(t0 >>> 24) & 0xff] ^ TS2[(t0 >>> 16) & 0xff] ^ TX1[(t0 >>> 8) & 0xff] ^ TX2[t0 & 0xff];
	t1 = TS1[(t1 >>> 24) & 0xff] ^ TS2[(t1 >>> 16) & 0xff] ^ TX1[(t1 >>> 8) & 0xff] ^ TX2[t1 & 0xff];
	t2 = TS1[(t2 >>> 24) & 0xff] ^ TS2[(t2 >>> 16) & 0xff] ^ TX1[(t2 >>> 8) & 0xff] ^ TX2[t2 & 0xff];
	t3 = TS1[(t3 >>> 24) & 0xff] ^ TS2[(t3 >>> 16) & 0xff] ^ TX1[(t3 >>> 8) & 0xff] ^ TX2[t3 & 0xff];
	t1 ^= t2;
	t2 ^= t3;
	t0 ^= t1;
	t3 ^= t1;
	t2 ^= t0;
	t1 ^= t2;
	t1 = badc(t1);
	t2 = cdab(t2);
	t3 = dcba(t3);
	t1 ^= t2;
	t2 ^= t3;
	t0 ^= t1;
	t3 ^= t1;
	t2 ^= t0;
	t1 ^= t2;

	if (keyBits > 128) {
	    w1[0] = toInt(mk[16], mk[17], mk[18], mk[19]);
	    w1[1] = toInt(mk[20], mk[21], mk[22], mk[23]);
	    if (keyBits > 192) {
		w1[2] = toInt(mk[24], mk[25], mk[26], mk[27]);
		w1[3] = toInt(mk[28], mk[29], mk[30], mk[31]);
	    } else {
		w1[2] = w1[3] = 0;
	    }
	} else {
	    w1[0] = w1[1] = w1[2] = w1[3] = 0;
	}
	w1[0] ^= t0;
	w1[1] ^= t1;
	w1[2] ^= t2;
	w1[3] ^= t3;
	t0 = w1[0];
	t1 = w1[1];
	t2 = w1[2];
	t3 = w1[3];

	q = (q == 2) ? 0 : (q + 1);
	t0 ^= KRK[q][0];
	t1 ^= KRK[q][1];
	t2 ^= KRK[q][2];
	t3 ^= KRK[q][3];
	t0 = TX1[(t0 >>> 24) & 0xff] ^ TX2[(t0 >>> 16) & 0xff] ^ TS1[(t0 >>> 8) & 0xff] ^ TS2[t0 & 0xff];
	t1 = TX1[(t1 >>> 24) & 0xff] ^ TX2[(t1 >>> 16) & 0xff] ^ TS1[(t1 >>> 8) & 0xff] ^ TS2[t1 & 0xff];
	t2 = TX1[(t2 >>> 24) & 0xff] ^ TX2[(t2 >>> 16) & 0xff] ^ TS1[(t2 >>> 8) & 0xff] ^ TS2[t2 & 0xff];
	t3 = TX1[(t3 >>> 24) & 0xff] ^ TX2[(t3 >>> 16) & 0xff] ^ TS1[(t3 >>> 8) & 0xff] ^ TS2[t3 & 0xff];
	t1 ^= t2;
	t2 ^= t3;
	t0 ^= t1;
	t3 ^= t1;
	t2 ^= t0;
	t1 ^= t2;
	t3 = badc(t3);
	t0 = cdab(t0);
	t1 = dcba(t1);
	t1 ^= t2;
	t2 ^= t3;
	t0 ^= t1;
	t3 ^= t1;
	t2 ^= t0;
	t1 ^= t2;
	t0 ^= w0[0];
	t1 ^= w0[1];
	t2 ^= w0[2];
	t3 ^= w0[3];
	w2[0] = t0;
	w2[1] = t1;
	w2[2] = t2;
	w2[3] = t3;

	q = (q == 2) ? 0 : (q + 1);
	t0 ^= KRK[q][0];
	t1 ^= KRK[q][1];
	t2 ^= KRK[q][2];
	t3 ^= KRK[q][3];
	t0 = TS1[(t0 >>> 24) & 0xff] ^ TS2[(t0 >>> 16) & 0xff] ^ TX1[(t0 >>> 8) & 0xff] ^ TX2[t0 & 0xff];
	t1 = TS1[(t1 >>> 24) & 0xff] ^ TS2[(t1 >>> 16) & 0xff] ^ TX1[(t1 >>> 8) & 0xff] ^ TX2[t1 & 0xff];
	t2 = TS1[(t2 >>> 24) & 0xff] ^ TS2[(t2 >>> 16) & 0xff] ^ TX1[(t2 >>> 8) & 0xff] ^ TX2[t2 & 0xff];
	t3 = TS1[(t3 >>> 24) & 0xff] ^ TS2[(t3 >>> 16) & 0xff] ^ TX1[(t3 >>> 8) & 0xff] ^ TX2[t3 & 0xff];
	t1 ^= t2;
	t2 ^= t3;
	t0 ^= t1;
	t3 ^= t1;
	t2 ^= t0;
	t1 ^= t2;
	t1 = badc(t1);
	t2 = cdab(t2);
	t3 = dcba(t3);
	t1 ^= t2;
	t2 ^= t3;
	t0 ^= t1;
	t3 ^= t1;
	t2 ^= t0;
	t1 ^= t2;
	w3[0] = t0 ^ w1[0];
	w3[1] = t1 ^ w1[1];
	w3[2] = t2 ^ w1[2];
	w3[3] = t3 ^ w1[3];

	gsrk(w0, w1, 19, rk, j);
	j += 4;
	gsrk(w1, w2, 19, rk, j);
	j += 4;
	gsrk(w2, w3, 19, rk, j);
	j += 4;
	gsrk(w3, w0, 19, rk, j);
	j += 4;
	gsrk(w0, w1, 31, rk, j);
	j += 4;
	gsrk(w1, w2, 31, rk, j);
	j += 4;
	gsrk(w2, w3, 31, rk, j);
	j += 4;
	gsrk(w3, w0, 31, rk, j);
	j += 4;
	gsrk(w0, w1, 67, rk, j);
	j += 4;
	gsrk(w1, w2, 67, rk, j);
	j += 4;
	gsrk(w2, w3, 67, rk, j);
	j += 4;
	gsrk(w3, w0, 67, rk, j);
	j += 4;
	gsrk(w0, w1, 97, rk, j);
	j += 4;
	if (keyBits > 128) {
	    gsrk(w1, w2, 97, rk, j);
	    j += 4;
	    gsrk(w2, w3, 97, rk, j);
	    j += 4;
	}
	if (keyBits > 192) {
	    gsrk(w3, w0, 97, rk, j);
	    j += 4;
	    gsrk(w0, w1, 109, rk, j);
	}
    }

    private static void doDecKeySetup(int[] rk, int keyBits) {
	int a = 0, z;
	int[] t = new int[4];

	z = 32 + keyBits / 8;
	swapBlocks(rk, 0, z);
	a += 4;
	z -= 4;

	for (; a < z; a += 4, z -= 4)
	    swapAndDiffuse(rk, a, z, t);
	diff(rk, a, t, 0);
	rk[a] = t[0];
	rk[a + 1] = t[1];
	rk[a + 2] = t[2];
	rk[a + 3] = t[3];
    }

    private static int toInt(byte b0, byte b1, byte b2, byte b3) {
	return (b0 & 0xff) << 24 ^ (b1 & 0xff) << 16 ^ (b2 & 0xff) << 8 ^ b3 & 0xff;
    }

    /*
    private static void toByteArray(int i, byte[] b, int offset) {
	b[offset] = (byte)(i >>> 24);
	b[offset + 1] = (byte)(i >>> 16);
	b[offset + 2] = (byte)(i >>> 8);
	b[offset + 3] = (byte)(i);
    }
    */

    private static int m(int t) {
	return 0x00010101 * ((t >>> 24) & 0xff) ^ 0x01000101 * ((t >>> 16) & 0xff) ^ 0x01010001 * ((t >>> 8) & 0xff) ^ 0x01010100 * (t & 0xff);
    }

    private static final int badc(int t) {
	return ((t << 8) & 0xff00ff00) ^ ((t >>> 8) & 0x00ff00ff);
    }

    private static final int cdab(int t) {
	return ((t << 16) & 0xffff0000) ^ ((t >>> 16) & 0x0000ffff);
    }

    private static final int dcba(int t) {
	return (t & 0x000000ff) << 24 ^ (t & 0x0000ff00) << 8 ^ (t & 0x00ff0000) >>> 8 ^ (t & 0xff000000) >>> 24;
    }

    private static final void gsrk(int[] x, int[] y, int rot, int[] rk, int offset) {
	int q = 4 - (rot / 32), r = rot % 32, s = 32 - r;

	rk[offset] = x[0] ^ y[(q) % 4] >>> r ^ y[(q + 3) % 4] << s;
	rk[offset + 1] = x[1] ^ y[(q + 1) % 4] >>> r ^ y[(q) % 4] << s;
	rk[offset + 2] = x[2] ^ y[(q + 2) % 4] >>> r ^ y[(q + 1) % 4] << s;
	rk[offset + 3] = x[3] ^ y[(q + 3) % 4] >>> r ^ y[(q + 2) % 4] << s;
    }

    private static final void diff(int[] i, int offset1, int[] o, int offset2) {
	int t0, t1, t2, t3;

	t0 = m(i[offset1]);
	t1 = m(i[offset1 + 1]);
	t2 = m(i[offset1 + 2]);
	t3 = m(i[offset1 + 3]);
	t1 ^= t2;
	t2 ^= t3;
	t0 ^= t1;
	t3 ^= t1;
	t2 ^= t0;
	t1 ^= t2;
	t1 = badc(t1);
	t2 = cdab(t2);
	t3 = dcba(t3);
	t1 ^= t2;
	t2 ^= t3;
	t0 ^= t1;
	t3 ^= t1;
	t2 ^= t0;
	t1 ^= t2;
	o[offset2] = t0;
	o[offset2 + 1] = t1;
	o[offset2 + 2] = t2;
	o[offset2 + 3] = t3;
    }

    private static final void swapBlocks(int[] arr, int offset1, int offset2) {
	int t;

	for (int i = 0; i < 4; i++) {
	    t = arr[offset1 + i];
	    arr[offset1 + i] = arr[offset2 + i];
	    arr[offset2 + i] = t;
	}
    }

    private static final void swapAndDiffuse(int[] arr, int offset1, int offset2, int[] tmp) {
	diff(arr, offset1, tmp, 0);
	diff(arr, offset2, arr, offset1);
	arr[offset2] = tmp[0];
	arr[offset2 + 1] = tmp[1];
	arr[offset2 + 2] = tmp[2];
	arr[offset2 + 3] = tmp[3];
    }

    private static void printBlock(PrintStream out, byte[] b) {
	for (int i = 0; i < 4; i++)
	    byteToHex(out, b[i]);
	out.print(" ");
	for (int i = 4; i < 8; i++)
	    byteToHex(out, b[i]);
	out.print(" ");
	for (int i = 8; i < 12; i++)
	    byteToHex(out, b[i]);
	out.print(" ");
	for (int i = 12; i < 16; i++)
	    byteToHex(out, b[i]);
    }

    /*
    private static void printEn_Decrypt(PrintStream out, byte[] b) {
	out.print(new String(b));
    }
    */

    /*
    private static void printSBox(PrintStream out, byte[] box) {
	for (int i = 0; i < 16; i++) {
	    for (int j = 0; j < 16; j++) {
		byteToHex(out, box[16 * i + j]);
		out.print(" ");
	    }
	    out.println();
	}
    }
    */

    private static void byteToHex(PrintStream out, byte b) {
	char[] buf = { HEX_DIGITS[(b >>> 4) & 0x0F], HEX_DIGITS[b & 0x0F] };
	out.print(new String(buf));
    }

    /*
    private static void intToHex(PrintStream out, int i) {
	byte[] b = new byte[4];
	toByteArray(i, b, 0);
	byteToHex(out, b[0]);
	byteToHex(out, b[1]);
	byteToHex(out, b[2]);
	byteToHex(out, b[3]);
    }
    */
    
    /*
    private static void printRoundKeys(PrintStream out, int[] roundKeys) {
	for (int i = 0; i < roundKeys.length;) {
	    out.print("* ");
	    intToHex(out, roundKeys[i++]);
	    out.print(" ");
	    intToHex(out, roundKeys[i++]);
	    out.print(" ");
	    intToHex(out, roundKeys[i++]);
	    out.print(" ");
	    intToHex(out, roundKeys[i++]);
	    out.print(" \n");
	}
    }
    */

    public static byte[] ARIA_test(byte[] param) throws InvalidKeyException {
	byte[] p = new byte[16];
	byte[] c = new byte[16];
	byte[] mk = new byte[32];

	//boolean flag = false;
	PrintStream out = System.out;
	ARIAEngine instance = new ARIAEngine(256);

	for (int i = 0; i < 32; i++)
	    mk[i] = 0;
	for (int i = 0; i < 16; i++)
	    p[i] = param[i];

	instance.setKey(mk);
	instance.setupRoundKeys();

	//out.print("plaintext : ");
	//printEn_Decrypt(out, p);
	//out.println();
	instance.encrypt(p, 0, c, 0);
	//out.print("ciphertext: ");
	printBlock(out, c);
	//out.println();
	instance.decrypt(c, 0, p, 0);
	//out.print("decrypted : ");
	//printEn_Decrypt(out, p);
	//out.println();
	return p;
    }

    public static void setMK(byte[] mk1) {
	for (int i = 0; i < 32; i++)
	    mk[i] = 0;

	for (int rr = 0; rr < mk1.length; rr++)
	    mk[rr] = mk1[rr];
    }

    public static byte[] Encrypt(byte[] param) throws InvalidKeyException {
	byte[] p = new byte[16];
	byte[] c = new byte[16];
	//byte[] mk = new byte[32];

	//boolean flag = false;
	//PrintStream out = System.out;
	ARIAEngine instance = new ARIAEngine(256);

	//  for (int i=0; i<32; i++)
	//      mk[i]=0;
	for (int i = 0; i < 16; i++)
	    p[i] = param[i];

	instance.setKey(mk);
	instance.setupRoundKeys();

	instance.encrypt(p, 0, c, 0);
	//out.print("ciphertext : ");
	//printEn_Decrypt(out, c);
	//out.println();
	return c;
    }

    public static byte[] Decrypt(byte[] param) throws InvalidKeyException {
	byte[] p = new byte[16];
	byte[] c = new byte[16];
	//byte[] mk = new byte[32];

	//boolean flag = false;
	//PrintStream out = System.out;
	ARIAEngine instance = new ARIAEngine(256);

	//for (int i=0; i<32; i++)
	//  mk[i]=0;
	for (int i = 0; i < 16; i++)
	    c[i] = param[i];

	instance.setKey(mk);
	instance.setupRoundKeys();

	instance.decrypt(c, 0, p, 0);
	//out.print("decrypted : ");
	//printEn_Decrypt(out, p);
	//out.println();
	return p;
    }
}

public class CipherUtil {
    /** 마스터 키 */
    String masterKey = null;

    /**
     * 암호 설정
     * 
     * @param mk_str 암호문자열
     */
    public void setPassword(String masterKey) {
	String key = masterKey;
	
	if (masterKey.length() > 32) {
	    key = key.substring(0, 32);
	} 
	
	this.masterKey = key;
	ARIAEngine.setMK(key.getBytes());
    }

    /**
     * 바이트 배열 리턴 암호화
     * 
     * @param enString 암호화할 문자열
     * @return 암호화된 바이트배열
     */
    public byte[] encrypt(String targetString) {
	String pp = targetString;
	byte[] rtn_byte = null;
	byte[] cipher = null;
	byte[] pp_arr = pp.getBytes();
	int a_1 = pp_arr.length / 16;
	int a_2 = pp_arr.length % 16;
	int a_3 = 16 - a_2;
	byte[] a_tmp = null;

	if (a_2 != 0)
	    a_tmp = new byte[pp_arr.length + a_3];
	else
	    a_tmp = new byte[pp_arr.length];

	for (int q = 0; q < a_tmp.length; q++)
	    a_tmp[q] = 0;

	for (int io = 0; io < pp_arr.length; io++)
	    a_tmp[io] = pp_arr[io];

	if (a_2 > 0)
	    a_1 += 1;

	byte[][] yu = new byte[a_1][16];

	int gh = 0;
	int jh = 0;
	for (int y = 0; y < a_tmp.length; y++) {

	    yu[gh][jh] = a_tmp[y];
	    jh++;

	    if ((y + 1) % 16 == 0) {
		gh++;
		jh = 0;
	    }
	}

	try {
	    byte[] p_tmp = new byte[a_tmp.length];
	    //byte[] p_tmp_ = new byte[16];
	    int po = 0;
	    for (int tt = 0; tt < a_1; tt++) {
		byte[] tmp = yu[tt];
		cipher = ARIAEngine.Encrypt(tmp);
		
//		for (int y = 0; y < cipher.length; y++) {
//		    p_tmp[po] = cipher[y];
//		    po++;
//		}
		//위의 for문을 수정
		System.arraycopy(cipher, 0, p_tmp, 0, cipher.length);
		
	    }
	    rtn_byte = p_tmp;
	} catch (Exception e) {
		System.err.println("CipherUtil encrypt() Exception Occured");
	}
	return rtn_byte;
    }

    /**
     * 바이트 배열 리턴 암호화
     * 
     * @param data 암호화할 바이트배열
     * @return 암호화된 바이트배열
     */
    public byte[] encrypt(byte[] data) {
	byte[] rtn_byte = null;
	byte[] cipher = null;
	byte[] pp_arr = data;
	int a_1 = pp_arr.length / 16;
	int a_2 = pp_arr.length % 16;
	int a_3 = 16 - a_2;
	byte[] a_tmp = null;

	if (a_2 != 0)
	    a_tmp = new byte[pp_arr.length + a_3];
	else
	    a_tmp = new byte[pp_arr.length];

	for (int q = 0; q < a_tmp.length; q++)
	    a_tmp[q] = 0;

	for (int io = 0; io < pp_arr.length; io++)
	    a_tmp[io] = pp_arr[io];

	if (a_2 > 0)
	    a_1 += 1;

	byte[][] yu = new byte[a_1][16];

	int gh = 0;
	int jh = 0;
	for (int y = 0; y < a_tmp.length; y++) {

	    yu[gh][jh] = a_tmp[y];
	    jh++;

	    if ((y + 1) % 16 == 0) {
		gh++;
		jh = 0;
	    }
	}

	try {
	    byte[] p_tmp = new byte[a_tmp.length];
	    //byte[] p_tmp_ = new byte[16];
	    int po = 0;
	    for (int tt = 0; tt < a_1; tt++) {
		byte[] tmp = yu[tt];
		cipher = ARIAEngine.Encrypt(tmp);
		
//		for (int y = 0; y < cipher.length; y++) {
//		    p_tmp[po] = cipher[y];
//		    po++;
//		}
		//위의 for문 변경
		System.arraycopy(cipher, 0, p_tmp, 0, cipher.length);
		
	    }
	    rtn_byte = p_tmp;
	} catch (Exception e) {
		System.err.println("CipherUtil encrypt() Exception Occured");
	}
	return rtn_byte;
    }

    /**
     * 복호화 배열 리턴 암호화
     * 
     * @param targetString 복호화할 데이타 바이트배열
     * @return 복호화된 바이트배열
     */
    public byte[] decrypt(byte[] targetString) {
	//String rtn_str = "";
	//byte[] cipher = null;
	byte[] rtn_arr = null;
	byte[] pp_arr = targetString;
	int a_1 = pp_arr.length / 16;
	int a_2 = pp_arr.length % 16;
	int a_3 = 16 - a_2;
	byte[] a_tmp = null;
	int gcount = 0;

	if (a_2 != 0)
	    a_tmp = new byte[pp_arr.length + a_3];
	else
	    a_tmp = new byte[pp_arr.length];

	for (int q = 0; q < a_tmp.length; q++)
	    a_tmp[q] = 0;

	for (int io = 0; io < pp_arr.length; io++)
	    a_tmp[io] = pp_arr[io];

	if (a_2 > 0)
	    a_1 += 1;

	byte[][] yu = new byte[a_1][16];

	int gh = 0;
	int jh = 0;
	for (int y = 0; y < a_tmp.length; y++) {

	    yu[gh][jh] = a_tmp[y];
	    jh++;

	    if ((y + 1) % 16 == 0) {
		gh++;
		jh = 0;
	    }
	}

	try {
	    byte[] p_tmp = new byte[a_tmp.length];
	    byte[] p_tmp_ = new byte[16];
	    int po = 0;
	    for (int tt = 0; tt < a_1; tt++) {
		byte[] tmp = yu[tt];
		p_tmp_ = ARIAEngine.Decrypt(tmp);
		
		//위쪽 루프문에서 복사하던 것을 System.arraycopy문을 사용해서 복사
		System.arraycopy(p_tmp_, 0, p_tmp, 0, p_tmp_.length);
		
		for (int y = 0; y < p_tmp_.length; y++) {
		    //p_tmp[po] = p_tmp_[y];
		    if (tt == (a_1 - 1) && p_tmp[po] == 0) {
			gcount++;
		    }
		    po++;
		}
	    }
	    rtn_arr = new byte[p_tmp.length - gcount];
	    for (int qq = 0; qq < rtn_arr.length; qq++)
		rtn_arr[qq] = p_tmp[qq];
	} catch (Exception e) {
		System.err.println("CipherUtil decrypt() Exception Occured");
	}
	return rtn_arr;
    }
    
}
